/*
 * @Author: ls
 * @Date: 2021-10-14 14:25:38
 * @LastEditTime: 2021-10-14 16:59:29
 * @LastEditors: Please set LastEditors
 * @Description: 画板
 * 可使用任意颜色绘制矩形，三角形，圆，直线，并统计每种颜色的像素个数
 * 擦除图案时，设置画板的颜色为透明色再进行绘制即可
 * @href https://forum.cocos.org/t/topic/89902
 * @FilePath: \drawing-board-master\assets\Script\DrawingBoard.ts
 */

export default class DrawingBoard {
	private _witdh: number;
	/**画板宽度 */
	public get width(): number {
		return this._witdh;
	}
	private _height: number;
	/**画板高度 */
	public get height(): number {
		return this._height;
	}

	/**记录每个像素点的颜色值的数组，颜色值用十六进制表示，RGBA格式 */
	private pointColor: number[][];
	/**记录每个像素点能否绘制，0表示不能绘制，1表示能绘制 */
	private maskPoint: number[][];
	/**存储像素数据的内存块 */
	private buffer: ArrayBuffer;
	/**颜色分量一维数组，供渲染使用 */
	private pixelColor: Uint8Array;
	/**记录各种颜色的像素的数量 */
	private colorCount: { [key: number]: number };

	/**记录最近一次绘制像素的颜色(十六进制颜色值)，调用绘制函数且未指定颜色值时，将使用该值 */
	private curColor: number;

	/**临时存储的颜色值 */
	private tempColor: number;
	private tempR: number;
	private tempG: number;
	private tempB: number;
	private tempA: number;

	/**
	 * 可对每个像素点绘制的画板，画板使用的坐标系原点为左下角，X轴向右为正，Y轴向上为正
	 * @param width     画板宽度
	 * @param height    画板高度
	 * @param data      指定画板初始内容，参数为记录颜色分量的一维数组，不传入参数时，画板中全部像素为透明
	 */
	public constructor(width: number, height: number, data?: ArrayBuffer) {
		this.init(width, height, data);
	}

	//#region 初始化
	/**
	 * 对画板进行初始化，会清空已绘制的所有内容
	 * @param width     画板宽度
	 * @param height    画板高度
	 * @param data      指定画板初始内容，参数为记录颜色分量的一维数组，不传入参数时，画板内容为全部透明的矩形
	 */
	public init(width: number, height: number, data?: ArrayBuffer) {
		this.tempColor = this.tempR = this.tempG = this.tempB = this.tempA = 0;
		this.curColor = 0;
		this._witdh = Math.round(width);
		this._height = Math.round(height);
		this.initPointColor();
		this.initMaskPoint();
		this.initPixelColor();
		this.initLineData();
		if (!!data) {
			this.setData(data);
		}
	}
	private initPointColor() {
		if (!this.pointColor) {
			this.pointColor = [];
		}
		for (let x = 0; x < this.width; ++x) {
			if (!this.pointColor[x]) {
				this.pointColor[x] = [];
			}
			for (let y = 0; y < this.height; ++y) {
				this.pointColor[x][y] = 0;
			}
		}
		this.colorCount = {};
		this.colorCount[0] = this.width * this.height;
	}
	private initMaskPoint() {
		if (!this.maskPoint) {
			this.maskPoint = [];
		}
		for (let x = 0; x < this.width; ++x) {
			if (!this.maskPoint[x]) {
				this.maskPoint[x] = [];
			}
			for (let y = 0; y < this.height; ++y) {
				this.maskPoint[x][y] = 1;
			}
		}
	}
	private initPixelColor() {
		this.buffer = new ArrayBuffer(this.width * this.height * 4);
		this.pixelColor = new Uint8Array(this.buffer);
		this.pixelColor.fill(0);
	}
	//#endregion

	//#region 重置内容
	/**重置画板，画板的宽高不变，但会清空已绘制的所有内容，恢复至透明状态 */
	public reset() {
		this.resetPointColor();
		this.resetMaskPoint();
		this.resetPixelColor();
	}
	private resetPointColor() {
		for (let x = this.width - 1; x >= 0; --x) {
			for (let y = this.height - 1; y >= 0; --y) {
				this.pointColor[x][y] = 0;
			}
		}
		for (let key in this.colorCount) {
			this.colorCount[key] = 0;
		}
	}
	private resetMaskPoint() {
		for (let x = this.width - 1; x >= 0; --x) {
			for (let y = this.height - 1; y >= 0; --y) {
				this.maskPoint[x][y] = 1;
			}
		}
	}
	private resetPixelColor() {
		this.pixelColor.fill(0);
	}
	//#endregion

	/**
	 * 传入图像的像素数据，直接设置画板的内容，图像尺寸必须与画板一致，若需要重新设置画板大小，请使用 init() 函数
	 * @param data 记录各像素颜色分量的一维数组
	 */
	public setData(data: ArrayBuffer) {
		let pixelData = new Uint8Array(data);
		if (pixelData.length != this.width * this.height * 4) {
			console.warn('画板设置数据失败，数据长度与画板大小不一致。');
			return;
		}
		this.setPixelColorByRGBA(pixelData);
		this.setPointColorByRGBA(pixelData);
	}
	/**
	 * 记录各像素颜色分量
	 * @param data 颜色分量一维数组
	 */
	private setPixelColorByRGBA(data: Uint8Array) {
		this.pixelColor.set(data);
	}
	/**
	 * 按像素点的坐标记录像素点的颜色值
	 * @param data 颜色分量一维数组
	 */
	private setPointColorByRGBA(data: Uint8Array) {
		this.colorCount = {};
		for (let y = 0; y < this.height; ++y) {
			let i = y * this.height;
			for (let x = 0; x < this.width; ++x) {
				let color = this.convertToNumber(data[i++], data[i++], data[i++], data[i++]);
				this.pointColor[x][y] = color;
				if (!this.colorCount[color]) {
					this.colorCount[color] = 1;
				} else {
					this.colorCount[color] += 1;
				}
			}
		}
	}

	/**
	 * 设置不能绘制图案的像素区域
	 * @param data  像素坐标为索引，0表示不能绘制，1表示能绘制
	 */
	public setMask(data: number[][]) {
		for (let x = this.width - 1; x >= 0; --x) {
			if (!!data[x]) {
				for (let y = this.height - 1; y >= 0; --y) {
					if (undefined != data[x][y]) {
						this.maskPoint[x][y] = data[x][y];
					}
				}
			}
		}
	}
	/**
	 * 设置画板为全部区域都不能绘制
	 */
	public setDisable() {
		for (let x = this.width - 1; x >= 0; --x) {
			for (let y = this.height - 1; y >= 0; --y) {
				this.maskPoint[x][y] = 0;
			}
		}
	}
	/**
	 * 在现有可绘制区域的基础上添加可以绘制的像素点区域。
	 * 此方法不会禁用画板当前可绘制的像素点区域，只会添加新的可绘制区域
	 * @param data  像素坐标为索引，0表示不能绘制，1表示能绘制
	 */
	public addEnablePoints(data: number[][]) {
		for (let x = this.width - 1; x >= 0; --x) {
			if (!!data[x]) {
				for (let y = this.height - 1; y >= 0; --y) {
					if (!!data[x][y]) {
						this.maskPoint[x][y] = data[x][y];
					}
				}
			}
		}
	}

	/**
	 * 获取画板中的数据
	 * @param data 用于接收数据的数组
	 * @returns {number[]} 返回存储各像素点颜色分量的一维数组
	 */
	public copyData(data?: number[]): number[] {
		if (undefined === data) {
			data = [];
		}
		for (let i = 0, count = this.pixelColor.length; i < count; ++i) {
			data[i] = this.pixelColor[i];
		}
		return data;
	}
	/**
	 * 获取画板中记录每个像素的颜色分量的数据
	 * @returns 将直接返回画板内部的数组；注：若使用者需要对该数据进行修改，请使用 copyData 方法获取，以免影响画板的像素个数计数功能
	 */
	public getData(): Uint8Array {
		return this.pixelColor;
	}
	/**获取画板内部使用的内存块，若仅需要获取像素数据，不进一步处理，使用 getData 即可 */
	public getBuffer(): ArrayBuffer {
		return this.buffer;
	}
	public getPointData(): number[][] {
		return this.pointColor;
	}
	/**
	 * 获取指定颜色的像素的个数
	 * @param r 颜色的r分量
	 * @param g 颜色的g分量
	 * @param b 颜色的b分量
	 * @param a 颜色透明度，默认为255
	 */
	public getColorCount(r: number, g: number, b: number, a: number = 255): number {
		let c = this.convertToNumber(r, g, b, a);
		return this.colorCount[c];
	}
	/**
	 * 设置画板绘制图案使使用的颜色
	 * @param r 包含RGBA分量的颜色对象，或者颜色的r分量
	 * @param g 颜色的g分量
	 * @param b 颜色的b分量
	 * @param a 颜色透明度，默认为255
	 */
	public setColor(r: number, g: number, b: number, a: number = 255) {
		this.curColor = this.convertToNumber(r, g, b, a);
		if (!this.colorCount[this.curColor]) {
			this.colorCount[this.curColor] = 0;
		}
		this.tempColor = this.curColor;
		this.tempR = r;
		this.tempG = g;
		this.tempB = b;
		this.tempA = a;
	}
	/**清空所有已绘制的内容 */
	public clear() {
		this.reset();
	}

	//#region 绘制：直线
	//直线
	/**上一次绘制的直线的终点 */
	private previousLineEndPos: Vec2;
	private previousLineEndPosT: Vec2;
	private previousLineEndPosB: Vec2;
	/**上一次绘制的直线的端点样式 */
	private previousLineCircleEnd: boolean;
	/**上一次绘制的直线的宽度 */
	private previousLineWidth: number;
	private initLineData() {
		this.previousLineEndPos = new Vec2();
		this.previousLineEndPosT = new Vec2();
		this.previousLineEndPosB = new Vec2();
		this.previousLineCircleEnd = true;
		this.previousLineWidth = 1;
	}
	/**
	 * 移动画笔到指定的位置，调用 lineTo 函数时将使用该点作为直线的起点
	 * @param x     坐标X
	 * @param y     坐标Y
	 */
	public moveTo(x: number, y: number) {
		x = Math.round(x);
		y = Math.round(y);
		this.previousLineEndPos.set(x, y);
		this.previousLineEndPosT.set(x, y);
		this.previousLineEndPosB.set(x, y);
	}
	/**
	 * 设置线宽
	 */
	public setLineWidth(w: number) {
		this.previousLineWidth = w;
	}
	/**
	 * 设置线段端点样式
	 * @param b 线段端点是否为圆形
	 */
	public setLineCircleEnd(b: boolean) {
		this.previousLineCircleEnd = b;
	}

	/**
	 * 绘制直线，使用默认的颜色、线宽和线段端点样式
	 * @param x1        起点坐标X
	 * @param y1        起点坐标Y
	 * @param x2        终点坐标X
	 * @param y2        终点坐标Y
	 */
	public line(x1: number, y1: number, x2: number, y2: number) {
		x1 = Math.round(x1);
		x2 = Math.round(x2);
		y1 = Math.round(y1);
		y2 = Math.round(y2);
		if (x1 == x2 && y1 == y2) return;
		let width = this.previousLineWidth;
		let circleEnd = this.previousLineCircleEnd;
		this.previousLineEndPos.set(x2, y2);
		let offsetX = 0;
		let offsetY = 0;
		let rateK = 1;
		if (x1 == x2) {
			offsetX = Math.round(width * 0.5);
		} else if (y1 == y2) {
			offsetY = Math.round(width * 0.5);
		} else {
			let k = (y2 - y1) / (x2 - x1);
			rateK = Math.sqrt(k * k + 1);
			offsetY = (width * 0.5) / rateK;
			offsetX = Math.round(offsetY * k);
			offsetY = Math.round(offsetY);
		}
		this.previousLineEndPosT.set(x2 - offsetX, y2 + offsetY);
		this.previousLineEndPosB.set(x2 + offsetX, y2 - offsetY);

		let p1 = new Vec2(x1, y1);
		let p2 = new Vec2(x2, y2);
		if (x1 > x2) {
			p1.x = x2;
			p1.y = y2;
			p2.x = x1;
			p2.y = y1;
		}
		this._drawLine(p1, p2, width, offsetX, offsetY, rateK);
		if (circleEnd) {
			this._drawCircle(x1, y1, width * 0.5);
			this._drawCircle(x2, y2, width * 0.5);
		}
	}
	/**
	 * 绘制到指定坐标的直线，起点为上一次绘制的直线的终点，使用默认的颜色、宽度和线段端点样式
	 * @param x     终点坐标X
	 * @param y     终点坐标Y
	 */
	public lineTo(x: number, y: number) {
		x = Math.round(x);
		y = Math.round(y);
		if (this.previousLineEndPos.x == x && this.previousLineEndPos.y == y) return;
		let width = this.previousLineWidth;
		let circleEnd = this.previousLineCircleEnd;
		let x1 = this.previousLineEndPos.x;
		let y1 = this.previousLineEndPos.y;
		let x2 = x;
		let y2 = y;
		if (x1 > x2) {
			x1 = x2;
			y1 = y2;
			x2 = this.previousLineEndPos.x;
			y2 = this.previousLineEndPos.y;
		}
		let offsetX = 0;
		let offsetY = 0;
		let rateK = 1;
		if (x1 == x2) {
			offsetX = Math.round(width * 0.5);
		} else if (y1 == y2) {
			offsetY = Math.round(width * 0.5);
		} else {
			let k = (y2 - y1) / (x2 - x1);
			rateK = Math.sqrt(k * k + 1);
			offsetY = (width * 0.5) / rateK;
			offsetX = Math.round(offsetY * k);
			offsetY = Math.round(offsetY);
		}
		if (!circleEnd) {
			if (this.previousLineEndPos.x != this.previousLineEndPosT.x || this.previousLineEndPos.y != this.previousLineEndPosT.y) {
				let p1 = new Vec2(this.previousLineEndPos.x - offsetX, this.previousLineEndPos.y + offsetY);
				let p2 = new Vec2(this.previousLineEndPos.x + offsetX, this.previousLineEndPos.y - offsetY);
				this._drawTriangle([p1, p2, this.previousLineEndPosT]);
				this._drawTriangle([p1, p2, this.previousLineEndPosB]);
			}
		} else {
			this._drawCircle(x1, y1, width * 0.5);
			this._drawCircle(x2, y2, width * 0.5);
		}
		this._drawLine(new Vec2(x1, y1), new Vec2(x2, y2), width, offsetX, offsetY, rateK);

		this.previousLineEndPos.set(x, y);
		this.previousLineEndPosT.set(x - offsetX, y + offsetY);
		this.previousLineEndPosB.set(x + offsetX, y - offsetY);
	}
	/**
	 * 绘制直线，不包含线段端点样式
	 * @param p1        线段起点坐标
	 * @param p2        线段终点坐标
	 * @param width     线段宽度
	 * @param color     线段颜色
	 */
	private _drawLine(p1: Vec2, p2: Vec2, width: number, offsetX: number, offsetY: number, slopeRate: number) {
		if (p1.y == p2.y) {
			//水平直线
			let x = p1.x < p2.x ? p1.x : p2.x;
			this._drawRect(new Vec2(x, Math.round(p1.y - width * 0.5)), Math.abs(p1.x - p2.x), width);
		} else if (p1.x == p2.x) {
			//垂直直线
			let y = p1.y < p2.y ? p1.y : p2.y;
			this._drawRect(new Vec2(Math.round(p1.x - width * 0.5), y), width, Math.abs(p1.y - p2.y));
		} else {
			//倾斜直线
			let inverseK = (p1.x - p2.x) / (p1.y - p2.y);
			let p1t = new Vec2(p1.x - offsetX, p1.y + offsetY);
			let p1b = new Vec2(p1.x + offsetX, p1.y - offsetY);
			let p2t = new Vec2(p2.x - offsetX, p2.y + offsetY);
			let p2b = new Vec2(p2.x + offsetX, p2.y - offsetY);
			let p1c = new Vec2();
			let p2c = new Vec2();
			let height = Math.round(width * slopeRate);
			if (p2.y > p1.y) {
				if (p1b.x < p2t.x) {
					p1c.x = p1b.x;
					p1c.y = p1b.y + height;
					p2c.x = p2t.x;
					p2c.y = p2t.y - height;
					this._drawVerticalTriangle(p1c, p1b, p1t);
					this._drawParallelogram(p1b, p2c, height);
					this._drawVerticalTriangle(p2t, p2c, p2b);
				} else {
					p1c.x = p1b.x;
					p1c.y = Math.round(p2t.y - (p1c.x - p2t.x) * inverseK);
					p2c.x = p2t.x;
					p2c.y = Math.round(p1b.y + (p1b.x - p2c.x) * inverseK);
					this._drawVerticalTriangle(p2t, p2c, p1t);
					this._drawParallelogram(p2c, p1b, p2t.y - p2c.y);
					this._drawVerticalTriangle(p1c, p1b, p2b);
				}
			} else {
				if (p1t.x < p2b.x) {
					p1c.x = p1t.x;
					p1c.y = p1t.y - height;
					p2c.x = p2b.x;
					p2c.y = p2b.y + height;
					this._drawVerticalTriangle(p1t, p1c, p1b);
					this._drawParallelogram(p1c, p2b, height);
					this._drawVerticalTriangle(p2c, p2b, p2t);
				} else {
					p1c.x = p1t.x;
					p1c.y = Math.round(p2b.y - (p1c.x - p2b.x) * inverseK);
					p2c.x = p2b.x;
					p2c.y = Math.round(p1t.y + (p1t.x - p2c.x) * inverseK);
					this._drawVerticalTriangle(p2c, p2b, p1b);
					this._drawParallelogram(p2b, p1c, p1t.y - p1c.y);
					this._drawVerticalTriangle(p1t, p1c, p2t);
				}
			}
		}
	}
	//#endregion

	//#region 绘制：矩形
	/**
	 * 绘制矩形
	 * @param x     矩形左下角的坐标X
	 * @param y     矩形左下角的坐标Y
	 * @param w     矩形宽度
	 * @param h     矩形高度
	 */
	public rect(x: number, y: number, w: number, h: number) {
		x = Math.round(x);
		y = Math.round(y);
		this._drawRect(new Vec2(x, y), w, h);
	}
	/**
	 * 绘制矩形
	 * @param p         矩形左下顶点的坐标
	 * @param w         矩形宽度
	 * @param h         矩形高度
	 * @param color     矩形填充的颜色
	 */
	private _drawRect(p: Vec2, w: number, h: number) {
		let minX = this.clampX(p.x);
		let maxX = this.clampX(p.x + w);
		let minY = this.clampY(p.y);
		let maxY = this.clampY(p.y + h);
		// for (let x = minX; x <= maxX; ++x) {
		//     for (let y = minY; y <= maxY; ++y) {
		//         this._drawPixel(x, y);
		//     }
		// }
		for (let y = minY; y <= maxY; ++y) {
			this._drawRowPixel(minX, maxX, y);
		}
	}
	/**
	 * 绘制平行四边形，平行四边形的左右两边与Y轴平行
	 * @param p1        左下顶点坐标
	 * @param p2        右下顶点坐标
	 * @param height    垂直边高度
	 * @param color     颜色
	 */
	private _drawParallelogram(p1: Vec2, p2: Vec2, height: number) {
		if (p1.x == p2.x) return;
		let k = (p2.y - p1.y) / (p2.x - p1.x);
		let minX = this._minX(p1.x);
		let maxX = this._maxX(p2.x);
		for (let x = minX; x <= maxX; ++x) {
			let minY = p1.y + Math.round((x - p1.x) * k);
			let maxY = minY + height;
			minY = this._minY(minY);
			maxY = this._maxY(maxY);
			this._drawColPixel(minY, maxY, x);
			// for (let y = minY; y <= maxY; ++y) {
			//     this._drawPixel(x, y);
			// }
		}
	}
	//#endregion

	//#region 绘制：三角形
	/**
	 * 绘制三角形
	 * @param x1    顶点1坐标X
	 * @param y1    顶点1坐标Y
	 * @param x2    顶点2坐标X
	 * @param y2    顶点2坐标Y
	 * @param x3    顶点3坐标X
	 * @param y3    顶点3坐标Y
	 */
	public triangle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
		x1 = Math.round(x1);
		y1 = Math.round(y1);
		x2 = Math.round(x2);
		y2 = Math.round(y2);
		x3 = Math.round(x3);
		y3 = Math.round(y3);
		let pList: Vec2[] = [];
		pList.push(new Vec2(x1, y1));
		pList.push(new Vec2(x2, y2));
		pList.push(new Vec2(x3, y3));
		this._drawTriangle(pList);
	}
	/**
	 * 绘制任意三角形
	 * @param p1    顶点坐标
	 * @param p2
	 * @param p3
	 * @param color 填充颜色
	 */
	private _drawTriangle(pList: Vec2[]) {
		pList.sort((a, b) => {
			return a.x - b.x;
		});
		let p1 = pList[0];
		let p2 = pList[1];
		let p3 = pList[2];
		if (p1.x == p2.x) {
			if (p1.x == p3.x) return;
			if (p1.y < p2.y) {
				p1 = pList[1];
				p2 = pList[0];
			}
			this._drawVerticalTriangle(p1, p2, p3);
			return;
		}
		let k = (p3.y - p1.y) / (p3.x - p1.x);
		let p4 = new Vec2(p2.x, Math.round(p1.y + (p2.x - p1.x) * k));
		if (p4.y == p2.y) return;
		if (p4.y < p2.y) {
			this._drawVerticalTriangle(p2, p4, p1);
			this._drawVerticalTriangle(p2, p4, p3);
		} else {
			this._drawVerticalTriangle(p4, p2, p1);
			this._drawVerticalTriangle(p4, p2, p3);
		}
	}
	/**
	 * 绘制一条边与Y轴平行的三角形
	 * @param p1    三角形垂直边的 上 顶点坐标
	 * @param p2    三角形垂直边的 下 顶点坐标
	 * @param p3    三角形 左侧或右侧 顶点坐标
	 * @param color 要绘制的颜色
	 */
	private _drawVerticalTriangle(p1: Vec2, p2: Vec2, p3: Vec2) {
		if (p3.x == p1.x) return;
		let k1 = (p3.y - p1.y) / (p3.x - p1.x);
		let k2 = (p3.y - p2.y) / (p3.x - p2.x);
		let maxX = p3.x,
			minX = p1.x;
		if (maxX < minX) {
			maxX = p1.x;
			minX = p3.x;
		}
		minX = this._minX(minX);
		maxX = this._maxX(maxX);
		for (let x = minX; x <= maxX; ++x) {
			let maxY = this.clampY(Math.round(p1.y + (x - p1.x) * k1));
			let minY = this.clampY(Math.round(p2.y + (x - p2.x) * k2));
			this._drawColPixel(minY, maxY, x);
			// for (let y = minY; y <= maxY; ++y) {
			//     this._drawPixel(x, y);
			// }
		}
	}
	//#endregion

	//#region 绘制：圆
	/**
	 * 绘制一个圆
	 * @param x         圆心坐标x
	 * @param y         圆心坐标y
	 * @param radius    圆的半径
	 */
	public circle(x: number, y: number, radius: number) {
		x = Math.round(x);
		y = Math.round(y);
		this._drawCircle(x, y, radius);
	}
	private _drawCircle(x: number, y: number, radius: number) {
		radius = Math.round(radius);
		if (radius == 0) return;
		//三角形的斜边的平方
		let dis = radius * radius;
		// let minX = this._minX(x - radius);
		// let maxX = this._maxX(x + radius);
		// for (let i = minX; i <= maxX; ++i) {
		//     let r = x - i;
		//     r = Math.round(Math.sqrt(dis - r * r));
		//     let minY = this._minY(y - r);
		//     let maxY = this._maxY(y + r);
		//     for (let j = minY; j <= maxY; ++j) {
		//         this._drawPixel(i, j);
		//     }
		// }
		let minY = this.clampY(y - radius);
		let maxY = this.clampY(y + radius);
		for (let j = minY; j <= maxY; ++j) {
			let r = j - y;
			r = Math.round(Math.sqrt(dis - r * r));
			let minX = this.clampX(x - r);
			let maxX = this.clampX(x + r);
			this._drawRowPixel(minX, maxX, j);
		}
	}
	//#endregion

	//#region 内部绘制方法
	private _minX(x: number): number {
		return x >= 0 ? x : 0;
	}
	private _maxX(x: number): number {
		return x < this.width ? x : this.width - 1;
	}
	private _minY(y: number): number {
		return y >= 0 ? y : 0;
	}
	private _maxY(y: number): number {
		return y < this.height ? y : this.height - 1;
	}
	private clampX(x: number): number {
		if (x < 0) return 0;
		if (x >= this.width) return this.width - 1;
		return x;
	}
	private clampY(y: number): number {
		if (y < 0) return 0;
		if (y >= this.height) return this.height - 1;
		return y;
	}
	/**绘制一个像素点的颜色 */
	private _drawPixel(x: number, y: number) {
		x = Math.round(x);
		y = Math.round(y);
		if (this.maskPoint[x][y] == 0) return;
		if (this.pointColor[x][y] == this.tempColor) return;
		let index = (y * this.width + x) * 4;
		this.pixelColor[index] = this.tempR;
		this.pixelColor[index + 1] = this.tempG;
		this.pixelColor[index + 2] = this.tempB;
		this.pixelColor[index + 3] = this.tempA;
		let c = this.pointColor[x][y];
		this.colorCount[c]--;
		this.colorCount[this.tempColor]++;
		this.pointColor[x][y] = this.tempColor;
	}
	/**
	 * 连续绘制一行中的像素点
	 * @param startX    起点X坐标
	 * @param endX      终点X坐标
	 * @param y         Y坐标
	 */
	private _drawRowPixel(startX: number, endX: number, y: number) {
		let index = (y * this.width + startX) * 4;
		for (let x = startX; x <= endX; ++x) {
			if (this.maskPoint[x][y] != 0 && this.pointColor[x][y] != this.tempColor) {
				this.pixelColor[index] = this.tempR;
				this.pixelColor[index + 1] = this.tempG;
				this.pixelColor[index + 2] = this.tempB;
				this.pixelColor[index + 3] = this.tempA;
				let c = this.pointColor[x][y];
				this.colorCount[c]--;
				this.colorCount[this.tempColor]++;
				this.pointColor[x][y] = this.tempColor;
			}
			index += 4;
		}
	}
	/**
	 * 连续绘制一列中的像素点
	 * @param startY    起点Y坐标
	 * @param endY      终点Y坐标
	 * @param x         X坐标
	 */
	private _drawColPixel(startY: number, endY: number, x: number) {
		let index = (startY * this.width + x) * 4;
		for (let y = startY; y <= endY; ++y) {
			if (this.maskPoint[x][y] != 0 && this.pointColor[x][y] != this.tempColor) {
				this.pixelColor[index] = this.tempR;
				this.pixelColor[index + 1] = this.tempG;
				this.pixelColor[index + 2] = this.tempB;
				this.pixelColor[index + 3] = this.tempA;
				let c = this.pointColor[x][y];
				this.colorCount[c]--;
				this.colorCount[this.tempColor]++;
				this.pointColor[x][y] = this.tempColor;
			}
			index += this.width * 4;
		}
	}
	/**
	 * 将RGBA颜色分量转换为一个数值表示的颜色，颜色分量为0~255之间的值
	 * @param r
	 * @param g
	 * @param b
	 * @param a
	 */
	private convertToNumber(r: number, g: number, b: number, a: number = 255): number {
		//颜色值将用于数组索引，不能为负数，故红色分量为奇数时将减1变为偶数
		return ((r & 0xfe) << 23) | (g << 16) | (b << 8) | a;
	}
	/**将十六进制的颜色转换为RGBA分量表示的颜色 */
	private convertToRGBA(color: number): { r: number; g: number; b: number; a: number } {
		//颜色值将用于数组索引，不能为负数，故红色分量为奇数时将减1变为偶数
		return {
			r: (color & 0xef000000) >> 23,
			g: (color & 0x00ff0000) >> 16,
			b: (color & 0x0000ff00) >> 8,
			a: color & 0x000000ff,
		};
	}
	//#endregion
}

class Vec2 {
	public x: number;
	public y: number;

	constructor(x: number = 0, y: number = 0) {
		this.x = x;
		this.y = y;
	}

	public set(p: number | Vec2, y?: number) {
		if (typeof p === 'number') {
			this.x = p;
			this.y = y;
		} else {
			this.x = p.x;
			this.y = p.y;
		}
	}
}
